package cn.turboinfo.fuyang.api.provider.common.service.impl.sms

import cn.turboinfo.fuyang.api.domain.common.service.SmsService
import cn.turboinfo.fuyang.api.entity.common.enumeration.sms.SmsType
import cn.turboinfo.fuyang.api.entity.common.exception.sms.SmsRateLimitedException
import cn.turboinfo.fuyang.api.entity.common.exception.sms.SmsVerifyUnmatchedException
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.data.redis.core.ValueOperations
import java.util.concurrent.TimeUnit
import kotlin.random.Random


abstract class AbstractSmsServiceImpl(
    protected val redisTemplate: StringRedisTemplate,
) : SmsService {

    protected val logger = LoggerFactory.getLogger(this::class.java)

    @Value("\${deploy.cache.key-prefix:}")
    private lateinit var cacheKeyPrefix: String

    protected val valueOperation: ValueOperations<String, String> = redisTemplate.opsForValue()

    private fun codeRateLimitKey(type: SmsType, phone: String): String {
        return "${cacheKeyPrefix}:sms:limit:${phone}:${type.name}"
    }

    private fun codeValidKey(type: SmsType, phone: String): String {
        return "${cacheKeyPrefix}:sms:valid:${phone}:${type.name}"
    }

    abstract fun invokeSendCode(type: SmsType, phone: String, code: String)

    override fun sendCode(type: SmsType, phone: String) {
        val limitKey = codeRateLimitKey(type, phone)
        if (redisTemplate.hasKey(limitKey)) {
            logger.error("超出短信验证码发送频率限制, type=${type}, phone=$phone")
            throw SmsRateLimitedException()
        }

        val code = (1..6).joinToString("") {
            Random.nextInt(0, 10).toString()
        }

        invokeSendCode(type, phone, code)

        valueOperation.set(codeValidKey(type, phone), code, getExpiredSeconds(), TimeUnit.SECONDS)
        valueOperation.set(limitKey, code, getRateLimitSeconds(), TimeUnit.SECONDS)
    }

    protected open fun getRateLimitSeconds(): Long {
        return 60
    }

    protected open fun getExpiredSeconds(): Long {
        return 600
    }

    override fun verifyCode(type: SmsType, phone: String, code: String) {
        val validKey = codeValidKey(type, phone)

        val savedCode = valueOperation.get(validKey) ?: run {
            logger.error("未找到已发送的验证码, type=${type}, phone=$phone")
            throw SmsVerifyUnmatchedException()
        }

        if (code != savedCode) {
            logger.error("验证码不一致, type=${type}, phone=$phone")
            throw SmsVerifyUnmatchedException()
        }
    }

}
